#include "MLInterStreamAnalysis.h"
#include <algorithm>

CMLInterStreamAnalysis::CMLInterStreamAnalysis(CMLInterStream* pStream, std::wofstream* pOutputFile)
{
	m_pStream = pStream;
	m_pOutputFile = pOutputFile;
	m_nRegressP1 = 2;
	m_nRegressP2 = 2;
	m_nDimension = 1;
	m_nTimeUnit = 1;
	m_nStartTime = -1;
}

CMLInterStreamAnalysis::~CMLInterStreamAnalysis(void)
{
}

int CMLInterStreamAnalysis::Insert(const CMLDataPoint& p, int nTime)
{
	int nRet;

	if (m_nStartTime == -1)
		m_nStartTime = nTime;

	// Insert the data point to data stream
	nRet = m_pStream->Insert(p, nTime);
	ML_ON_FAILED_RETURN(nRet);

	if (m_pStream->GetClusters().size() > 0)
	{
		if (nTime >= m_nPeriodStart && nTime < m_nPeriodEnd) 
		{
			if ((nTime - m_nStartTime)%m_nTimeUnit == 0)
			{
				// Record the clusters' information
				nRet = RecordClusterStates(nTime);
				ML_ON_FAILED_RETURN(nRet);

				// Record the centroid at intervention event I
				if (nTime/m_nTimeUnit == m_nI/m_nTimeUnit)
				{
					RecordCentroid();
				}
			}
		}
	}
	return 0;
}

int CMLInterStreamAnalysis::InterventionAnalyze(void)
{
	int nRet;
	std::map<int, std::vector<ClusterState_>>::iterator Iter;
	std::map<double, std::wstring> TopErrs;
	std::map<double, int> TopErrIds;
	std::list<std::wstring> lists;
	int p1 = m_nRegressP1;
	int p2 = m_nRegressP2;
	int I = m_nI;
	int start = m_nPeriodStart;
	int end = m_nPeriodEnd;
	int dimension = m_nDimension + 1;
	CMLDataPoint chisqs;
	CMLDataPoint zero(dimension);

	std::vector<std::wstring> vReports;
	for (Iter=m_ClusterSamples.begin(); Iter!=m_ClusterSamples.end(); Iter++)
	{
		CMLDataSet preInter;
		CMLDataSet errors;
		CMLDataSet predicts;
		std::wstringstream report;

		// Collection the time-series data
		const std::vector<ClusterState_>& states = (*Iter).second;
		(*m_pOutputFile)<<L"==== id :" << (*Iter).first << L'\n';

		if (states.back().time < I + 2*p2*m_nTimeUnit || (states.back().time-states.front().time) < p2*m_nTimeUnit)
			continue;

		for (int i=0; i<(int)states.size(); i++)
		{
			const ClusterState_& state = states[i];
			if (state.time < I)
				preInter.Insert(state.af);
		}
		report << L"id:\t"<<(*Iter).first << L'\n';
		
		if (states.front().time < I - 2*p1*m_nTimeUnit)
		{
			// Put to train Autoregression model
			std::vector<CMLDataPoint> coefficents;
			nRet = CMLRegress_Linear::AR_Regress(dimension, preInter, p1, coefficents, chisqs);
			ML_ON_FAILED_RETURN(nRet);

			// Print the coefficents
			(*m_pOutputFile)<<L"preintervention coefficents:\n";
			for (int d=0; d<dimension; d++)
			{
				std::wcout<<L"d"<<d<<L"\t" <<coefficents[d].ToWString(L'\t')<<L'\n';
				(*m_pOutputFile)<<L"d"<<d<<L"\t" <<coefficents[d].ToWString(L'\t')<<L'\n';
			}
			(*m_pOutputFile)<<L"chi-squares : \n"<<chisqs.ToWString(L'\t')<<L'\n';
			(*m_pOutputFile)<<L"chi-squares norm2: \n"<<chisqs.Norm2()<<L'\n';
			report << "PreInter Chi-Squares\t"<<chisqs.Norm2()<<L'\n';

			// Predict the values after intervention
			for (int i=0; i<p1-1; i++)
				predicts.Insert(preInter.Get(preInter.Count() - (p1-1)+i));
			nRet = CMLRegress_Linear::AR_Predict(dimension, predicts, p1, coefficents, (end-I)/m_nTimeUnit);
			ML_ON_FAILED_RETURN(nRet);
		}
		else
		{
			// Print the coefficents
			(*m_pOutputFile)<<L"preintervention coefficents: Null\n";
			(*m_pOutputFile)<<L"chi-squares : null\n";
			(*m_pOutputFile)<<L"chi-squares norm2: null\n";
			report << "PreInter Chi-Squares\tnull\n";
			CMLDataPoint afnull(dimension);
			for (int i=0; i<dimension-1; i++)
				afnull[i] = m_Centroid[i];
			afnull[dimension-1] = 0;
			for (int i=0; i<p1-1+(end-I)/m_nTimeUnit; i++)
				predicts.Insert(afnull);
		}

		// Get absoluted error
		int errIndex=0;
		double errNorm=0;
		(*m_pOutputFile)<<L"errors:\n";
		for (int i=0; i<(int)states.size(); i++)
		{
			const ClusterState_& state = states[i];
			if (state.time >= I)
			{
				const ClusterState_& post = state;
				const CMLDataPoint& predict = predicts.Get((post.time-I)/m_nTimeUnit+p2-1);
				CMLDataPoint abserr = post.af - predict;
				errors.Insert(abserr);
				(*m_pOutputFile)<<abserr.ToWString(L'\t')<<L'\n';
				errIndex++;
				errNorm += ClusterState_::affect(abserr);
			}
		}
		
		// Regress the intervention value
		std::vector<CMLDataPoint> InterCoefficents;
		std::vector<int> PrjInterCoes;
		nRet = CMLRegress_Linear::AR_Regress(dimension, errors, p2, InterCoefficents, chisqs);

		
		// Print the coefficents
		(*m_pOutputFile)<<L"intervention coefficents:\n";
		for (int d=0; d<dimension; d++)
		{
			int k;
			for (k=0;k<InterCoefficents[d].size();k++)
			{
				if (abs(InterCoefficents[d][k]) < m_CoeThreshold[k])
					break;
			}
			if (k==(int)InterCoefficents[d].size())
				PrjInterCoes.push_back(d);
			(*m_pOutputFile)<<L"d"<<d<<L"\t" <<InterCoefficents[d].ToWString(L'\t')<<L'\n';
			for (int i=0;i<(int)states.size(); i++)
			{
				const ClusterState_& state = states[i];
				if (state.time >= I-8*m_nTimeUnit && state.time<I+8*m_nTimeUnit)
				{
					(*m_pOutputFile) <<L"id\t"<<(*Iter).first<<L'\t'<<L"d"<<d<<L"\t"<< "Time\t"<<state.time<< "\t"<<state.af[d]<<'\n';
				}
			}
		}
		(*m_pOutputFile)<<L"chi-squares : \n"<<chisqs.ToWString(L'\t')<<L'\n';
		(*m_pOutputFile)<<L"chi-squares norm2: \n"<<chisqs.Norm2()<<L'\n';
		report << "PostInter Chi-Squares\t"<<chisqs.Norm2()<<L'\n';
		// Print the projected coefficients
		(*m_pOutputFile)<<L"projected intervention coefficents:\n";
		std::wstringstream dimensionseries;
		for (int d=0; d<(int)PrjInterCoes.size(); d++)
		{
			(*m_pOutputFile)<<L"d"<<PrjInterCoes[d]<<L"\t" <<InterCoefficents[PrjInterCoes[d]].ToWString(L'\t')<<L'\n';
			report <<L"d"<<PrjInterCoes[d]<<L"\t" <<InterCoefficents[PrjInterCoes[d]].ToWString(L'\t')<<L'\n';
			for (int i=0;i<(int)states.size(); i++)
			{
				const ClusterState_& state = states[i];
				if (state.time >= I-8*m_nTimeUnit && state.time<I+8*m_nTimeUnit)
				{
					dimensionseries <<L"id\t"<<(*Iter).first<<L'\t'<<L"d"<<PrjInterCoes[d]<<L"\t"<< "Time\t"<<state.time<< "\t"<<state.af[PrjInterCoes[d]]<<'\n';
				}
			}
		}
		report <<dimensionseries.str();
		(*m_pOutputFile)<<L"affect = "<<errNorm<<"\n";
		TopErrs[1.0/errNorm] = report.str();
		std::wcout<<L"====\n";
		(*m_pOutputFile)<<L"====\n";
	}
	(*m_pOutputFile)<<L"==== Reports : \n";
	std::map<double, std::wstring>::const_iterator topReportIter = TopErrs.begin();
	for (int i=0; i<10; i++)
	{
		(*m_pOutputFile)<<(*topReportIter).second;
		topReportIter++;
	}
	(*m_pOutputFile)<<L"====\n";

	return 0;
}

int CMLInterStreamAnalysis::AutoRegress(void)
{
	int nRet;
	std::map<int, std::vector< ClusterState_>>::iterator Iter;
	for (Iter=m_ClusterSamples.begin(); Iter!=m_ClusterSamples.end(); Iter++)
	{
		CMLDataSet dataSet;
		// Collection the time-series data
		std::vector<ClusterState_>& states = (*Iter).second;
		for (int i=0; i<(int)states.size(); i++)
		{
			ClusterState_& state = states[i];
			dataSet.Insert(state.cf);
		}
		// Put to train Autoregression model
		std::vector<CMLDataPoint> coefficents;
		nRet = CMLRegress_Linear::AR_Regress(2*m_nDimension+1, dataSet, m_nRegressP1, coefficents);
		ML_ON_FAILED_RETURN(nRet);
		// Print the coefficents
		for (int d=0; d<m_nDimension; d++)
		{
			std::wcout<<coefficents[d].ToWString()<<L'\n';
		}
		std::wcout<<L"====\n";
	}

	return 0;
}

int CMLInterStreamAnalysis::RecordClusterStates(int nTime)
{
	int nCluIndex;
	std::vector<CMLInterStreamCluster*>& vClusters = m_pStream->GetClusters();
	for (nCluIndex=0; nCluIndex<(int)vClusters.size(); nCluIndex++)
	{
		ClusterState_ cluState;
		const CMLInterStreamCluster* cluster = vClusters[nCluIndex];
		int nCluId = cluster->GetID();
		// Construct the cluster state information
		cluState.center = cluster->GetCenter();
		cluState.radius = cluster->GetRadius();
		cluState.count  = cluster->GetSWLength();
		cluState.cf = cluster->GetCF();
		cluState.af = cluster->GetAf();
		cluState.time = nTime;
		// record in the map of clusters' samples
		m_ClusterSamples[nCluId].push_back(cluState);
	}
	return 0;
}

int CMLInterStreamAnalysis::RecordCentroid()
{
	int nCluIndex;
	std::vector<CMLInterStreamCluster*>& vClusters = m_pStream->GetClusters();
	m_Centroid = vClusters[0]->GetCenter()*vClusters[0]->GetSWLength();
	for (nCluIndex=1; nCluIndex<(int)vClusters.size(); nCluIndex++)
	{
		m_Centroid.add(vClusters[0]->GetCenter()*vClusters[0]->GetSWLength());
	}
	m_Centroid.div(m_pStream->GetTimeHorizon());
	return 0;
}

int CMLInterStreamAnalysis::SaveClusterRecords(const std::wstring& wstrFilename)
{
	FILE* fp;
	_wfopen_s(&fp,wstrFilename.c_str(), L"wb");
	std::map<int, std::vector<ClusterState_>>::iterator Iter;
	for (Iter = m_ClusterSamples.begin(); Iter!=m_ClusterSamples.end(); Iter++)
	{
		const std::vector<ClusterState_>& states = (*Iter).second;
		for (int i=0; i<(int)states.size(); i++)
		{
			const ClusterState_& state = states[i];
			for (int d=0; d<state.af.size(); d++)
			{
				float f = (float)state.af[d];
				fprintf(fp, "%f\t",f);
				// fwrite(&f,sizeof(float),1, fp);
			}
			fprintf(fp,"\n");
			//fwrite(&state.time, sizeof(int), 1, fp);
		}
	}
	fclose(fp);
	return 0;
}

